#ifndef _INCLUDED_DISPATCH_CONNECTION_H_
#define _INCLUDED_DISPATCH_CONNECTION_H_

#include <iomanip>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>

#include "rpc.h"
#include "rpc_function_table.h"
using boost::asio::ip::tcp;

class dispatch_connection : public boost::enable_shared_from_this<dispatch_connection>
{
public:
	dispatch_connection(boost::asio::io_service& io_service)
		: socket_(io_service)
	{}
	~dispatch_connection(){
	}
	tcp::socket& socket(){return socket_;}
	template<typename Handler>
	void start(Handler handler)
	{
		auto f = [=](const boost::system::error_code& error,size_t bytes_transferred){
				this->handle_read_header(error,bytes_transferred,handler);
			};
		boost::asio::async_read(socket_,
			boost::asio::buffer(inbound_header_),
			f);
	}
	template<typename Handler>
	void handle_read_header(const boost::system::error_code& error,
		size_t bytes_transferred,Handler handler)
	{
		if(!error)
		{
			//
			std::istringstream is(std::string(inbound_header_,header_length));
			size_t inbound_data_size = 0;
			if( !(is >> std::hex >> inbound_data_size))
			{
				boost::system::error_code error(boost::asio::error::invalid_argument);
				handler(error, shared_from_this());
				return;
			}
			inbound_data_.resize(inbound_data_size);
			auto f = [=](const boost::system::error_code& error,
			size_t bytes_transferred){
			this->handle_read_data(error,bytes_transferred,handler);
			};
			boost::asio::async_read(socket_,
				boost::asio::buffer(inbound_data_,inbound_data_size),
				f);
		}
		else
		{
			handler(error, shared_from_this());
			return;
		}
	}

	template<typename Handler>
	void handle_read_data(const boost::system::error_code& error,
		size_t bytes_transferred,Handler handler)
	{
		if(!error)
		{
			if(inbound_data_.size() < sizeof(unsigned int)) 
			{
				handler(error, shared_from_this());
				return;
			}
			rpc_message msg(inbound_data_);
			auto fhandler = [=](const boost::system::error_code& ec,const std::vector<char>& result){
				this->handle_write(ec,result,handler);
			};
			static rpc_function_table<decltype(fhandler)> function_tbl;
			function_tbl.dispatch_rpc_function(msg,fhandler);
		}
		else
		{
			handler(error, shared_from_this());
			return;
		}
	}

	template<typename Handler>
	void handle_write(const boost::system::error_code& error,const std::vector<char>& result,Handler handler)
	{
		if(!error)
		{
			//socket_.async_
			std::ostringstream header_stream;
			header_stream << std::setw(header_length)
				<<std::hex << result.size();
			if(!header_stream )
			{
				boost::system::error_code error(boost::asio::error::invalid_argument);
				handler(error,shared_from_this());
				return;
			}
			outbound_header_ = header_stream.str();
			outbound_data_.resize(result.size());
			std::copy(result.begin(),result.end(),outbound_data_.begin());
			std::vector<boost::asio::const_buffer> buffers;
			buffers.push_back(boost::asio::buffer(outbound_header_));
			buffers.push_back(boost::asio::buffer(outbound_data_));
			//
			boost::asio::async_write(socket_, buffers, 
				[=](const boost::system::error_code& ec,size_t)
				{
					handler(ec, shared_from_this());
			    }
			);
		}
		else
		{
			handler(error,shared_from_this());
			return;
		}
	}
private:
	tcp::socket socket_;
	enum {header_length = 8};

	std::string outbound_header_;
	std::string outbound_data_;

	char inbound_header_[header_length];
	std::vector<char> inbound_data_;
};

typedef boost::shared_ptr<dispatch_connection> dispatch_connection_ptr;


#endif